// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package fcgi

// 该文件从子进程的角度实现了FastCGI。

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/cgi"
	"os"
	"strings"
	"time"
)

// 请求保存正在进行的请求的状态。完成后，
// 它将转换为http.Request。
type request struct {
	pw        *io.PipeWriter
	reqId     uint16
	params    map[string]string
	buf       [1024]byte
	rawParams []byte
	keepConn  bool
}

// envVarsContextKey唯一标识CGI 
// 环境变量到其在请求上下文中的值的映射
type envVarsContextKey struct{}

func newRequest(reqId uint16, flags uint8) *request {
	r := &request{
		reqId:    reqId,
		params:   map[string]string{},
		keepConn: flags&flagKeepConn != 0,
	}
	r.rawParams = r.buf[:0]
	return r
}

// parseParams将编码的[]字节读入参数。
func (r *request) parseParams() {
	text := r.rawParams
	r.rawParams = nil
	for len(text) > 0 {
		keyLen, n := readSize(text)
		if n == 0 {
			return
		}
		text = text[n:]
		valLen, n := readSize(text)
		if n == 0 {
			return
		}
		text = text[n:]
		if int(keyLen)+int(valLen) > len(text) {
			return
		}
		key := readString(text, keyLen)
		text = text[keyLen:]
		val := readString(text, valLen)
		text = text[valLen:]
		r.params[key] = val
	}
}

// 响应实现http.ResponseWriter。
type response struct {
	req            *request
	header         http.Header
	code           int
	wroteHeader    bool
	wroteCGIHeader bool
	w              *bufWriter
}

func newResponse(c *child, req *request) *response {
	return &response{
		req:    req,
		header: http.Header{},
		w:      newWriter(c.conn, typeStdout, req.reqId),
	}
}

func (r *response) Header() http.Header {
	return r.header
}

func (r *response) Write(p []byte) (n int, err error) {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	if !r.wroteCGIHeader {
		r.writeCGIHeader(p)
	}
	return r.w.Write(p)
}

func (r *response) WriteHeader(code int) {
	if r.wroteHeader {
		return
	}
	r.wroteHeader = true
	r.code = code
	if code == http.StatusNotModified {
		// 不能有正文。
		r.header.Del("Content-Type")
		r.header.Del("Content-Length")
		r.header.Del("Transfer-Encoding")
	}
	if r.header.Get("Date") == "" {
		r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	}
}

// writeCGIHeader完成发送到客户端的头并将其写入输出。
// p不是由writeHeader编写的，而是将要编写的正文
// 的第一块。如果未显式设置
// 则会嗅探内容类型。
func (r *response) writeCGIHeader(p []byte) {
	if r.wroteCGIHeader {
		return
	}
	r.wroteCGIHeader = true
	fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
	if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType {
		r.header.Set("Content-Type", http.DetectContentType(p))
	}
	r.header.Write(r.w)
	r.w.WriteString("\r\n")
	r.w.Flush()
}

func (r *response) Flush() {
	if !r.wroteHeader {
		r.WriteHeader(http.StatusOK)
	}
	r.w.Flush()
}

func (r *response) Close() error {
	r.Flush()
	return r.w.Close()
}

type child struct {
	conn    *conn
	handler http.Handler

	requests map[uint16]*request // 由请求ID键入
}

func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child {
	return &child{
		conn:     newConn(rwc),
		handler:  handler,
		requests: make(map[uint16]*request),
	}
}

func (c *child) serve() {
	defer c.conn.Close()
	defer c.cleanUp()
	var rec record
	for {
		if err := rec.read(c.conn.rwc); err != nil {
			return
		}
		if err := c.handleRecord(&rec); err != nil {
			return
		}
	}
}

var errCloseConn = errors.New("fcgi: connection should be closed")

var emptyBody = io.NopCloser(strings.NewReader(""))

// 当处理程序尝试读取已被web服务器中止的请求的
// 正文时，ERRRESTEXTABORD通过Read返回。
var ErrRequestAborted = errors.New("fcgi: request aborted by web server")

// 当处理程序在关闭与web服务器的连接后尝试读取
// 请求的正文时，ERRCONNCCLOSED通过Read返回。
var ErrConnClosed = errors.New("fcgi: connection to web server closed")

func (c *child) handleRecord(rec *record) error {
	req, ok := c.requests[rec.h.Id]
	if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
		// 规范要求忽略未知的请求ID。
		return nil
	}

	switch rec.h.Type {
	case typeBeginRequest:
		if req != nil {
			// 服务器正在尝试以与正在进行的请求相同的ID 
			// 开始请求。这是一个错误。
			return errors.New("fcgi: received ID that is already in-flight")
		}

		var br beginRequest
		if err := br.read(rec.content()); err != nil {
			return err
		}
		if br.role != roleResponder {
			c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole)
			return nil
		}
		req = newRequest(rec.h.Id, br.flags)
		c.requests[rec.h.Id] = req
		return nil
	case typeParams:
		// 注意（eds）：从技术上讲，一个键值对可以跨越两个数据包之间的边界。我们会缓冲，直到我们收到所有参数。
		if len(rec.content()) > 0 {
			req.rawParams = append(req.rawParams, rec.content()...)
			return nil
		}
		req.parseParams()
		return nil
	case typeStdin:
		content := rec.content()
		if req.pw == nil {
			var body io.ReadCloser
			if len(content) > 0 {
				// body可以是一个io.LimitReader，但只要双方都有行为，它就不重要了。
				body, req.pw = io.Pipe()
			} else {
				body = emptyBody
			}
			go c.serveRequest(req, body)
		}
		if len(content) > 0 {
			// TODO（eds）：在处理程序从管道中读取数据之前，此操作一直处于阻塞状态。
			// 如果处理程序花费很长时间，可能会出现问题。
			req.pw.Write(content)
		} else {
			delete(c.requests, req.reqId)
			if req.pw != nil {
				req.pw.Close()
			}
		}
		return nil
	case typeGetValues:
		values := map[string]string{"FCGI_MPXS_CONNS": "1"}
		c.conn.writePairs(typeGetValuesResult, 0, values)
		return nil
	case typeData:
		// 如果实现了过滤器角色，请在此处读取数据流。
		return nil
	case typeAbortRequest:
		delete(c.requests, rec.h.Id)
		c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
		if req.pw != nil {
			req.pw.CloseWithError(ErrRequestAborted)
		}
		if !req.keepConn {
			// 返回时连接将关闭
			return errCloseConn
		}
		return nil
	default:
		b := make([]byte, 8)
		b[0] = byte(rec.h.Type)
		c.conn.writeRecord(typeUnknownType, 0, b)
		return nil
	}
}

// filterOutUsedEnvVars返回一个新的环境变量映射，其中没有
// 在给定的环境变量映射中为创建每个http而读取的变量。请求
func filterOutUsedEnvVars(envVars map[string]string) map[string]string {
	withoutUsedEnvVars := make(map[string]string)
	for k, v := range envVars {
		if addFastCGIEnvToContext(k) {
			withoutUsedEnvVars[k] = v
		}
	}
	return withoutUsedEnvVars
}

func (c *child) serveRequest(req *request, body io.ReadCloser) {
	r := newResponse(c, req)
	httpReq, err := cgi.RequestFromMap(req.params)
	if err != nil {
		// 读取请求
		r.WriteHeader(http.StatusInternalServerError)
		c.conn.writeRecord(typeStderr, req.reqId, []byte(err.Error()))
	} else {
		httpReq.Body = body
		withoutUsedEnvVars := filterOutUsedEnvVars(req.params)
		envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars)
		httpReq = httpReq.WithContext(envVarCtx)
		c.handler.ServeHTTP(r, httpReq)
	}
	// 确保我们提供了服务即使没有向r 
	r.Write(nil)
	r.Close()
	c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)

	// 写入任何内容，也会消耗整个身体，因此当我们在中关闭下面的套接字时，主机不会仍然向
	// 我们写入内容！请继续，
	// 否则我们会发送RST。（golang.org/issue/4183）
	// TODO（bradfitz）：也及时装订了这一份。或者向主机发送
	// 某种中止请求，这样主机
	// 就可以正确地切断发送所有数据的客户端。
	// 现在只绑定一点，
	io.CopyN(io.Discard, body, 100<<20)
	body.Close()

	if !req.keepConn {
		c.conn.Close()
	}
}

func (c *child) cleanUp() {
	for _, req := range c.requests {
		if req.pw != nil {
			// 在c.serveRequest中与Close的调用竞争并不重要，因为
			// Pipe（Reader | Writer）.Close是幂等的
			req.pw.CloseWithError(ErrConnClosed)
		}
	}
}

// Serve接受侦听器l上传入的FastCGI连接，为每个侦听器创建一个新的
// goroutine。goroutine读取请求，然后调用处理程序
// 来回复请求。
// 如果l为零，Serve将接受来自os.Stdin的连接。
// 如果处理程序为nil，则使用http.DefaultServeMux。
func Serve(l net.Listener, handler http.Handler) error {
	if l == nil {
		var err error
		l, err = net.FileListener(os.Stdin)
		if err != nil {
			return err
		}
		defer l.Close()
	}
	if handler == nil {
		handler = http.DefaultServeMux
	}
	for {
		rw, err := l.Accept()
		if err != nil {
			return err
		}
		c := newChild(rw, handler)
		go c.serve()
	}
}

// ProcessEnv返回与请求相关联的FastCGI环境变量r 
// ，请求本身中未包含该变量-数据
// 隐藏在请求的上下文中。例如，如果为
// 请求设置了REMOTE_USER，则在r中的任何位置都找不到它，但它将包含在
// ProcessEnv的响应中（通过r的上下文）。
func ProcessEnv(r *http.Request) map[string]string {
	env, _ := r.Context().Value(envVarsContextKey{}).(map[string]string)
	return env
}

// addFastCGIEnvToContext报告是否在http.Request.Context中包含FastCGI环境变量s 
// 可通过ProcessEnv访问。
func addFastCGIEnvToContext(s string) bool {
	// 本机排除net/http支持的内容：
	switch s {
	case "CONTENT_LENGTH", "CONTENT_TYPE", "HTTPS",
		"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR",
		"REMOTE_HOST", "REMOTE_PORT", "REQUEST_METHOD",
		"REQUEST_URI", "SCRIPT_NAME", "SERVER_PROTOCOL":
		return false
	}
	if strings.HasPrefix(s, "HTTP_") {
		return false
	}
	// 显式包含特定于FastCGI的内容。
	// 此列表与下面的默认“return true”是多余的。ABCFDG 
	switch s {
	case "REMOTE_USER":
		return true
	}
	// 未知，因此为了安全起见，请将其包括在内。
	return true
}
